﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Uow;
using Xms.Abp.Caching;
using Xms.Abp.Domain;
using Xms.Abp.Domain.Repositories;
using Xms.Abp.Settings;

namespace SettingsManagement;

public class SettingsStore : XmsDomainService, ISettingsStore, ITransientDependency
{
    protected ISettingsCacheContext SettingsCacheContext => LazyInject<ISettingsCacheContext>();
    protected ISettingsDefinitionManager SettingsDefinitionManager => LazyInject<SettingsDefinitionManager>();
    protected IXmsRepository<Setting, Guid> SettingRepository => LazyInject<IXmsRepository<Setting, Guid>>();

    protected IHashCache<string> GetSettings(string typeName, string providerName, string providerKey)
    {
        return SettingsCacheContext.GetSettings(CalculateCacheKey(typeName, providerName, providerKey));
    }

    [UnitOfWork]
    public virtual async Task<string> GetOrNullAsync(string typeName, string name, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var setting = await SettingRepository.GetAsync(r=>r.Key == typeName && r.Name == name && r.ProviderName == providerName && r.ProviderKey == providerKey, cancellationToken: cancellationToken);
        return setting?.Value;
        //return (await GetCacheItemAsync(typeName, name, providerName, providerKey));
    }

    [UnitOfWork]
    public virtual async Task SetAsync(string typeName, string name, string value, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var setting = await SettingRepository.FirstOrDefaultAsync(s => s.Key == typeName && s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey);
        if (setting == null)
        {
            setting = new Setting(GuidGenerator.Create(), typeName, name, value, providerName, providerKey);
            await SettingRepository.InsertAsync(setting, cancellationToken: cancellationToken);
        }
        else
        {
            setting.Value = value;
            await SettingRepository.UpdateAsync(setting, cancellationToken: cancellationToken);
        }

        //var cache = GetSettings(typeName, providerName, providerKey);

        //await cache.SetAsync(name, setting.Value, considerUow: true, cancellationToken: cancellationToken);
    }

    [UnitOfWork]
    public virtual async Task<List<SettingValue>> GetListAsync(string typeName, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var list = await (await SettingRepository.AsNoTrackingAsync()).Where(r => r.Key == typeName && r.ProviderName == providerName && r.ProviderKey == providerKey).ToListAsync(cancellationToken);
        return list.Select(s => new SettingValue(s.Name, s.Value)).ToList();
        //var cache = GetSettings(typeName, providerName, providerKey);
        //var list = await cache.GetAllAsync(considerUow: true, cancellationToken: cancellationToken);
        //return list.Select(s => new SettingValue(s.Key, s.Value)).ToList();
    }

    [UnitOfWork]
    public virtual async Task DeleteAsync(string typeName, string name, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var setting = await SettingRepository.FirstOrDefaultAsync(s => s.Key == typeName && s.Name == name && s.ProviderName == providerName && s.ProviderKey == providerKey, cancellationToken);
        if (setting != null)
        {
            await SettingRepository.DeleteAsync(setting, cancellationToken: cancellationToken);

            //var cache = GetSettings(typeName, providerName, providerKey);
            //await cache.RemoveAsync(name, considerUow: true, cancellationToken: cancellationToken);
        }
    }

    protected virtual async Task<string> GetCacheItemAsync(string typeName, string name, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var cache = GetSettings(typeName, providerName, providerKey);
        var value = await cache.GetAsync(name, considerUow: true);

        return value;
    }

    [UnitOfWork]
    public async Task<List<SettingValue>> GetListAsync(string typeName, string[] names, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        Check.NotNullOrEmpty(names, nameof(names));

        List<SettingValue> result;

        if (names.Length == 1)
        {
            var name = names.First();
            result = new List<SettingValue>();
            result.Add(new SettingValue(name, (await GetOrNullAsync(typeName, name, providerName, providerKey, cancellationToken))));
            //result.Add(new SettingValue(name, (await GetCacheItemAsync(typeName, name, providerName, providerKey, cancellationToken))));
            return result;
        }

        //var cacheItems = await GetCacheItemsAsync(typeName, names, providerName, providerKey, cancellationToken);
        //foreach (var item in cacheItems)
        //{
        //    result.Add(new SettingValue(item.Key, item.Value));
        //}
        result = await (await SettingRepository.AsNoTrackingAsync()).Where(s => s.Key == typeName && names.Contains(s.Name) && s.ProviderName == providerName && s.ProviderKey == providerKey).Select(r => new SettingValue { Name = r.Name, Value = r.Value }).ToListAsync(cancellationToken);

        return result;
    }

    protected virtual async Task<List<KeyValuePair<string, string>>> GetCacheItemsAsync(string typeName, string[] names, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var cache = GetSettings(typeName, providerName, providerKey);
        var cacheItems = (await cache.GetManyAsync(names, considerUow: true, cancellationToken: cancellationToken)).ToList();

        if (cacheItems.All(x => x.Value != null))
        {
            return cacheItems;
        }

        var notCacheNames = cacheItems.Where(x => x.Value == null).Select(x => x.Key).ToList();

        var newCacheItems = await SetCacheItemsAsync(typeName, providerName, providerKey, notCacheNames, cancellationToken);

        var result = new List<KeyValuePair<string, string>>();
        foreach (var name in names)
        {
            var item = newCacheItems.FirstOrDefault(x => x.Key == name);
            if (item.Value == null)
            {
                item = cacheItems.FirstOrDefault(x => x.Key == name);
            }

            result.Add(new KeyValuePair<string, string>(name, item.Value));
        }

        return result;
    }

    private async Task<List<KeyValuePair<string, string>>> SetCacheItemsAsync(
        string typeName,
        string providerName,
        string providerKey,
        List<string> notCacheNames,
        CancellationToken cancellationToken = default)
    {
        var settingsTypeDefinition = SettingsDefinitionManager.GetType(typeName);
        var settingDefinitions = settingsTypeDefinition.GetSettings().Where(r => notCacheNames.Contains(r.Name));

        var names = notCacheNames.ToArray();
        var settingsDictionary = (await (await SettingRepository.AsNoTrackingAsync()).Where(r=>names.Contains(r.Name) && r.ProviderName == providerName && r.ProviderKey == providerKey).ToListAsync(cancellationToken))
            .ToDictionary(s => s.Name, s => s.Value);

        var cacheItems = new List<KeyValuePair<string, string>>();

        foreach (var settingDefinition in settingDefinitions)
        {
            var settingValue = settingsDictionary.GetOrDefault(settingDefinition.Name);
            cacheItems.Add(new KeyValuePair<string, string>(settingDefinition.Name, settingValue));
        }

        var cache = GetSettings(typeName, providerName, providerKey);
        await cache.SetManyAsync(cacheItems, considerUow: true, cancellationToken: cancellationToken);

        return cacheItems;
    }


    private const string CacheKeyFormat = "Settings:{0}:{1}:{2}";
    protected virtual string CalculateCacheKey(string typeName, string providerName, string providerKey)
    {
        return string.Format(CacheKeyFormat, providerName, providerKey, typeName);
    }

    [UnitOfWork]
    public async Task<List<SettingValue>> GetAllAsync(string typeName, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        return await (await SettingRepository.AsNoTrackingAsync()).Where(s => s.Key == typeName && s.ProviderName == providerName && s.ProviderKey == providerKey).Select(r => new SettingValue { Name = r.Name, Value = r.Value }).ToListAsync(cancellationToken);
        //return await GetListAsync(typeName, providerName, providerKey, cancellationToken);
    }

    [UnitOfWork]
    public async Task SetAsync(string typeName, List<SettingValue> values, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        var names = values.Select(r => r.Name);
        var settings = (await (await SettingRepository.GetQueryableAsync()).Where(s => s.Key == typeName && names.Contains(s.Name) && s.ProviderName == providerName && s.ProviderKey == providerKey).ToListAsync()).ToDictionary(r => r.Name);
        foreach (var value in values)
        {
            if (settings.ContainsKey(value.Name))
            {
                var setting = settings[value.Name];
                setting.Value = value.Value;
                await SettingRepository.UpdateAsync(setting, cancellationToken: cancellationToken);
            }
            else
            {
                var setting = new Setting(GuidGenerator.Create(), value.Name, value.Value, providerName, providerKey);
                await SettingRepository.InsertAsync(setting, cancellationToken: cancellationToken);
                settings[value.Name] = setting;
            }
        }

        //var cacheItems = settings.ToDictionary(r => r.Key, r => r.Value.Value);
        //var cache = GetSettings(typeName, providerName, providerKey);
        //await cache.SetManyAsync(cacheItems, considerUow: true, cancellationToken: cancellationToken);
    }

    [UnitOfWork]
    public async Task DeleteAsync(string typeName, string providerName, string providerKey, CancellationToken cancellationToken = default)
    {
        await SettingRepository.DeleteManyAsync(s => s.Key == typeName && s.ProviderName == providerName && s.ProviderKey == providerKey, cancellationToken: cancellationToken);
        //var cache = GetSettings(typeName, providerName, providerKey);
        //await cache.RemoveAsync(cancellationToken: cancellationToken);
    }
}
